import { colors } from "consola/utils";
import type { PrerenderRoute } from "nitropack/types";
import { parseURL } from "ufo";
import { parse as parseHTML, walk } from "ultrahtml";

const allowedExtensions = new Set(["", ".json"]);

const linkParents = new Map<string, Set<string>>();

const HTML_ENTITIES = {
  "&lt;": "<",
  "&gt;": ">",
  "&amp;": "&",
  "&apos;": "'",
  "&quot;": '"',
} as Record<string, string>;

function escapeHtml(text: string) {
  return text.replace(
    /&(lt|gt|amp|apos|quot);/g,
    (ch) => HTML_ENTITIES[ch] || ch
  );
}

export async function extractLinks(
  html: string,
  from: string,
  res: Response,
  crawlLinks: boolean
) {
  const links: string[] = [];
  const _links: string[] = [];

  // Extract from any <TAG href=""> to crawl
  if (crawlLinks) {
    await walk(parseHTML(html), (node) => {
      if (!node.attributes?.href) {
        return;
      }

      const link = escapeHtml(node.attributes.href);
      if (
        !decodeURIComponent(link).startsWith("#") &&
        allowedExtensions.has(getExtension(link))
      ) {
        _links.push(link);
      }
    });
  }

  // Extract from x-nitro-prerender headers
  const header = res.headers.get("x-nitro-prerender") || "";
  _links.push(...header.split(",").map((i) => decodeURIComponent(i.trim())));

  for (const link of _links.filter(Boolean)) {
    const _link = parseURL(link);
    if (_link.protocol || _link.host) {
      continue;
    }
    if (!_link.pathname.startsWith("/")) {
      const fromURL = new URL(from, "http://localhost");
      _link.pathname = new URL(_link.pathname, fromURL).pathname;
    }
    links.push(_link.pathname + _link.search);
  }
  for (const link of links) {
    const _parents = linkParents.get(link);
    if (_parents) {
      _parents.add(from);
    } else {
      linkParents.set(link, new Set([from]));
    }
  }
  return links;
}

const EXT_REGEX = /\.[\da-z]+$/;

function getExtension(link: string): string {
  const pathname = parseURL(link).pathname;
  return (pathname.match(EXT_REGEX) || [])[0] || "";
}

export function formatPrerenderRoute(route: PrerenderRoute) {
  let str = `  ├─ ${route.route} (${route.generateTimeMS}ms)`;

  if (route.error) {
    const parents = linkParents.get(route.route);
    const errorColor =
      colors[route.error.statusCode === 404 ? "yellow" : "red"];
    const errorLead = parents?.size ? "├──" : "└──";
    str += `\n  │ ${errorLead} ${errorColor(route.error.message)}`;

    if (parents?.size) {
      str += `\n${[...parents.values()]
        .map((link) => `  │ └── Linked from ${link}`)
        .join("\n")}`;
    }
  }

  if (route.skip) {
    str += colors.gray(" (skipped)");
  }

  return colors.gray(str);
}

// prettier-ignore
type IgnorePattern =
  | string
  | RegExp
  | ((path: string) => undefined | null | boolean);
export function matchesIgnorePattern(path: string, pattern: IgnorePattern) {
  if (typeof pattern === "string") {
    // TODO: support radix3 patterns
    return path.startsWith(pattern as string);
  }

  if (typeof pattern === "function") {
    return pattern(path) === true;
  }

  if (pattern instanceof RegExp) {
    return pattern.test(path);
  }

  return false;
}
